package mymyoux.ai
{
	import flash.net.registerClassAlias;
	import flash.utils.getQualifiedClassName;
	
	import mymyoux.signals.Signal;
	import mymyoux.utils.Classes;
	import mymyoux.utils.Maths;

	/**
	 * Neuron (input, output or not)
	 */ 
	public class Neuron
	{
		/**
		 * @private
		 */
		private static const ALIAS:* = registerClassAlias( "mymyoux.ai.Neuron", Neuron );
		/**
		 * @private
		 */
		private var _weights:Vector.<Number>;
		/**
		 * input neurons
		 */
		private var _neurons:Vector.<Neuron>;
		/**
		 * inputs values
		 */
		protected var _inputs:Vector.<Number>;
		/**
		 * @private
		 */
		protected var _signalResult:Signal;
		/**
		 * value
		 */
		protected var _value:Number;
		/**
		 * Number of outputs (<b>deprecated</b>)
		 */
		private var _outputs:uint;
		/**
		 * ID (<b>deprecated</b>)
		 */
		protected var _id:int;
		/**
		 * Create a Neuron instance
	     */
		public function Neuron()
		{
			_id = Maths.unique;
			_weights = new Vector.<Number>(1);
			_inputs = new Vector.<Number>(1);
			_neurons = new Vector.<Neuron>(1);
			_signalResult = new Signal(Number);
			//value of the first input
			_inputs[0] = -1;
			_outputs = 0;
			_weights[0] = undefined;
			_value = undefined;
		}
		/**
		 * dispatches when calculs are done dispatch(Number);
		 */
		public function get signalResult():Signal
		{
			return _signalResult;
		}
		/*
		public function get weight0():Number
		{
			return _weights[0];
		}
		public function set weight0(value:Number):void
		{
			_weights[0] = value;
		}*/
		/**
		 * weights values
		 */
		public function get weights():Vector.<Number>
		{
			var id:int;
			for(id = 0; id<_weights.length; id++)
			{
				if(isNaN(_weights[id]))
				{
					_weights[id] = Maths.randNumberBetween(-30,30);
				}
			}
			return _weights;
		}
		public function set weights(val:Vector.<Number>):void
		{
			_weights = val;
		}
		/**
		 * register a neuron. This neuron will became an input
		 * @param neuron Input neuron for this instance.
		 */
		public function register(neuron:Neuron):void
		{
			_neurons.push(neuron);
			_inputs.push(undefined);
			_weights.push(undefined);
			neuron.signalResult.add(onInput, neuron);
			//not final
			neuron.outputs++;
		}
		/**
		 * Get 'input' neurons for this neuron.
		 * @return Linked neurons
		 */
		public function getLinkedNeurons():Vector.<Neuron>
		{
			return _neurons;
		}
		/**
		 * Number of input neurons for this instance. Including nerves
		 */
		public function get inputs():uint
		{
			return _inputs.length-1;
		}
		/**
		 * Indicates if there is others neurons connected to it
		 */
		public function get isInput():Boolean
		{
			var p:String;
			for(p in _neurons)
			{
				if(mymyoux.utils.Classes.isClassOf(_neurons[p], Neuron))
				{
					return false;	
				}
			}
			return true;
		}
		/**
		 * Number of output neurons
		 */
		public function get outputs():uint
		{
			return _outputs;
		}
		public function set outputs(value:uint):void
		{
			_outputs = value;
		}
		/**
		 * Return a chain representing the neuron
		 * @return chain
		 */
		public function toString():String
		{
			var result:String = "[Neuron("+_id+") inputs=\""+(_inputs.length-1)+"\" weights=\"{";
			var i:uint;
			for(i=0; i< _weights.length; i++)
			{
				result+=_weights[i]+";";
			}
			result = result.substring(0, result.length-1)+"}\"";
			if(inputs == 0)
			{
				result+=" input";
			}
			if(outputs == 0)
			{
				result+=" output";
			}else
			{
				result +=" ("+outputs+")";
			}
			result+="]";
			return result;
		}
		/**
		 * Called on input of a parent neuron
		 * @param value Value dispatched by the neuron
		 * @param neuron Parent neuron
		 */
		private function onInput(value:Number, neuron:Neuron):void
		{
			var id:int;
			if((id = _neurons.indexOf(neuron))>-1)
			{
				_inputs[id] = value;
			}
			for(id = 0; id<_inputs.length; id++)
			{
				if(isNaN(_inputs[id]))
				{
					return;
				}
				if(isNaN(_weights[id]))
				{
					_weights[id] = Maths.randNumberBetween(-30,30);
				}
			}
			execute();
			
		}
		/**
		 * Return a Long string
		 * @param id To avoid a recursive error
		 * @return Chain
		 */
		public function toLongString(id:uint = 0):String
		{
			var result:String;
			var p:String;
			result ="[Neuron ("+_id+")";
			try
			{
				if(id >15)
				{
					result +=" recursiveLimit";
				}else
				{
					
					for(p in _neurons)
					{
						if(_neurons[p] != null)
						{
							result+=" "+_neurons[p].toLongString(id + 1);	
						}
						
					}	
				}	
			}
			catch(error:Error)
			{
				result+=" recursiveError";
			}
			result+="]";
			return result;
		}
		/**
		 * Made the calculation
		 */
		protected function execute():void
		{
			var value:Number;
			var id:int;
			value = 0;
			for(id = 0; id<_inputs.length; id++)
			{
				value += _weights[id] * _inputs[id];
			}
			
			value = 1 / (1 + Math.exp(-value/1));
			trace(_id+":"+value);
			signalResult.dispatch(value);
			_inputs = new Vector.<Number>(_inputs.length);
			_inputs[0] = -1;
			for(id = 1; id <_inputs.length ; id++)
			{
				_inputs[id] = undefined;
			}
		}
	}
}